Software programs are continually becoming more complicated. Early programs consisted of straightforward procedural code that presented a simple, command line interface and text display to the user. These simple programs have gradually been replaced with complex programs that have graphical user interfaces and multiple features.
As programs have grown in complexity, the amount of effort which is required to write and debug the programs has also increased drastically. Consequently, major efforts have been made to reduce the amount of programming necessary to produce a modern, full-featured product. One of the most successful of these efforts has been the development of object-oriented programming in which programs are designed as collections of discrete elements called "objects". The objects can be modified and reused in many cases, thereby reducing the development effort.
As will be understood by those skilled in the art, objects in the context of object-oriented programming are software entities comprising data and operations on that data. The objects exist only at program runtime and are created, or instantiated, from object "classes" which are actually written by the programmer. The class code written by a programmer can be "reused" by another programmer by instantiating objects from that code. In addition, a developer can reuse class code by deriving new classes from existing classes by a process called inheritance. As is well-known, derived classes, or subclasses, inherit the characteristics of the classes from which they are derived. Inherited characteristics can be overridden and modified to customize the derived class.
In order to further ease the development burden, many products that support object-oriented programming provide libraries of classes that a developer can use and reuse by instantiation and subclassing as described above. With such libraries and the principles of inheritance, a developer can create and use families of related objects that share the same interface even though their contents and behavior differ.
Even though class libraries are very useful, they suffer from some drawbacks. For example, a large class library may include hundreds of classes in some cases and the relationship of classes can be very confusing. Unless a developer has detailed documentation, it may be difficult to understand what functions the classes are supposed to implement. It may also be difficult to make changes and additions without a thorough understanding of class relationships and operation. In addition, a program written with class libraries must still be responsible for the flow of control or the interactions between the objects instantiated from the classes.
Consequently, another approach called "frameworks" have been used to further reduce application development effort. A framework is a related set of classes which generate a set of objects that collaborate to solve a specific problem and provide a pre-fabricated structure of a working application. Since frameworks are based on object technology, the behavior of their objects can be inherited and overridden to allow developers to extend the framework and create customized solutions in a particular area of expertise. However, the problem which is solved by the framework is at a much higher level than that solved by the individual objects in a class library. For example, the objects in a framework might provide a basic word processor application whereas a class library might provide the individual functions of a word processor, such as selecting and copying text strings.
Problems remain when either class libraries or frameworks are used to develop an application program. These problems arise because the class libraries and frameworks can be changed and a new version of a class library or framework might eliminate classes or add new classes. Later when application programs written with the two different versions of the framework try to communicate and exchange objects, problems arise because the objects understood by both programs are different.
For example, programs often communicate by streaming data from one program to another. In such a streaming operation, a stream writer flattens, or marshals, object information to form a serial data stream. This object information takes the form of the class information which was used to create the object. The class information can be the original class information which was used to create the original object or, if the original object was an extension of another object, only the extension information may be sent.
The serial data stream is then sent to another application where a stream reader, resurrects, or de-marshals, the serial data stream to reconstruct a copy of the original object. If the class information was sent, it is used to construct the object. Alternatively, if only the extension data was sent, it is combined with class information already present at the destination and used to construct the object. Ordinarily, such an operation does not present a problem, but there can be difficulties when application programs built from two different framework versions attempt to communicate. Obviously, there may be classes unknown to a current framework version on streams which have been written by future versions because future versions can contain new classes. But future versions may not recognize some of the classes on streams from past versions if some of the old classes have been deleted in the new version. In many frameworks, the stream reader is intelligent and can recognize whether object data that it is reading corresponds to class data in the framework of which it is a part. When the stream reader encounters object data that it does not recognize, it may simply indicate that the stream is unreadable.
One prior art method of dealing with this problem is to include an object version number at the beginning of the stream. The stream writer inserts the object version number before writing any of the class elements to the stream. When the stream reader receives the stream it reads the object version and then can deal with the object appropriately. For example, a common way of dealing with different versions is to require a stream writer in a new framework version, which has a new streaming format, to increase the version number before writing the number to the stream. In addition, the stream reader in the new framework version must be able to read the older version class. This prior art solution works well when a new version stream reader reads old version files, but any file that includes a new version class will not be readable with an old framework version stream reader. The problem may be further exacerbated with stream writers which do not indicate the ends of streamed objects. In this case, the stream reader cannot simply skip the unknown class information and any stream which contains an unknown class is unreadable from that class onward.
Therefore, it would be desirable to have a framework system with both backward A data compatibility and forward data compatibility so that application programs designed with different versions of the framework could communicate. Backward data compatibility is the ability to read streams generated by previous versions of the framework, while forward data compatibility is the ability to read streams generated by future versions of the framework.